package com.dlc.shop.seckill.common.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.dlc.shop.bean.app.dto.ShopCartItemDto;
import com.dlc.shop.bean.app.dto.ShopCartOrderDto;
import com.dlc.shop.bean.app.dto.ShopCartOrderMergerDto;
import com.dlc.shop.bean.enums.OrderType;
import com.dlc.shop.bean.model.Order;
import com.dlc.shop.common.bo.MultiDecrementBO;
import com.dlc.shop.common.config.Constant;
import com.dlc.shop.common.constants.SegmentIdKey;
import com.dlc.shop.common.enums.MultiStockEnum;
import com.dlc.shop.common.exception.YamiShopBindException;
import com.dlc.shop.common.util.IdUtil;
import com.dlc.shop.common.util.RedisUtil;
import com.dlc.shop.manager.impl.StockManager;
import com.dlc.shop.seckill.common.constant.SeckillCacheNames;
import com.dlc.shop.seckill.common.model.SeckillSku;
import com.dlc.shop.seckill.common.service.SeckillCacheManager;
import com.dlc.shop.seckill.common.service.SeckillService;
import com.dlc.shop.seckill.common.service.SeckillSkuService;
import com.dlc.shop.service.OrderService;
import com.dlc.shop.service.SegmentService;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.aop.framework.AopContext;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;


/**
 * @author LGH
 */
@Slf4j
@Service
@AllArgsConstructor
public class SeckillCacheManagerImpl implements SeckillCacheManager {

    private final SegmentService segmentService;
    private final SeckillService seckillService;
    private final SeckillSkuService seckillSkuService;
    private final StockManager stockManager;
    private final OrderService orderService;


    @Override
    public String createOrderPath(String userId) {

        String limitKey = SeckillCacheNames.SECKILL_LIMIT_PREFIX + userId;
        int maxCount = 5;

        // 秒杀次数+1
        long seckillNum = RedisUtil.incr(limitKey, 1);

        // 5秒只能提交5次请求
        if (seckillNum == 1) {
            // 5秒后失效
            RedisUtil.expire(limitKey, 5);
        }
        if (seckillNum >= maxCount) {
            // 请求过频繁，请稍后再试
            throw new YamiShopBindException("yami.request.too.much");
        }
        String orderPath = IdUtil.encode(segmentService.getSegmentId(SegmentIdKey.SECKILL_ORDER_PATH));

        // 保存秒杀路径，5分钟这个路径就失效
        RedisUtil.set(getPathKey(userId), orderPath, 300);

        return orderPath;
    }

    @Override
    public void checkOrderPath(String userId, String orderPath) {
        String cacheOrderPath = RedisUtil.get(getPathKey(userId));

        if (!Objects.equals(cacheOrderPath, orderPath)) {
            // 订单已过期，请重新选择商品进行秒杀
            throw new YamiShopBindException("yami.order.expired");
        }
    }

    private String getPathKey(String userId) {
        return SeckillCacheNames.SECKILL_PATH_PREFIX + userId;
    }


    @Override
    public void decrSeckillSkuStocks(MultiDecrementBO multiDecrementBO) {
        stockManager.decrSeckillStocks(multiDecrementBO);
    }
    @Override
    public void decrSeckillSkuStocks(ShopCartOrderMergerDto mergerOrder, Integer maxNum) {
        // 1.扣除秒杀sku库存
        ShopCartOrderDto shopCartOrderDto = mergerOrder.getShopCartOrders().get(0);
        ShopCartItemDto shopCartItemDto = shopCartOrderDto.getShopCartItemDiscounts().get(0).getShopCartItems().get(0);
        MultiDecrementBO multiDecrementBO = new MultiDecrementBO(MultiStockEnum.SECKILL, mergerOrder.getUserId(), shopCartOrderDto.getOrderNumber(),
                mergerOrder.getSeckillId(), shopCartItemDto.getSkuId(), shopCartItemDto.getProdCount(), maxNum, null);
        multiDecrementBO.setStockPointId(shopCartItemDto.getStockPointId());
        multiDecrementBO.setProdName(shopCartItemDto.getProdName());

        this.decrSeckillSkuStocks(multiDecrementBO);
//
//
//        // 获取秒杀缓存
//        Long cacheStocks = this.getStockBySeckillSkuId(seckillSkuId);
//        // 库存判断
//        if (cacheStocks != null && cacheStocks <= 0) {
//            // 库存不足
//            throw new YamiShopBindException("yami.insufficient.inventory");
//        }
//        String key = SeckillCacheNames.SECKILL_SKU_STOCKS_PREFIX + seckillSkuId;
//        if (cacheStocks == null || cacheStocks < prodCount || RedisUtil.decr(key, prodCount) < 0) {
//            RedisUtil.expire(key, 30);
//            // 本轮商品已被秒杀完毕，还有用户未支付，还有机会哟
//            throw new YamiShopBindException("yami.seckill.complete");
//        }
//        // 商品数超阈值 or 剩余库存为阈值的倍数，就将缓存中的库存同步到mysql中（未满足条件的可以通过定时任务实现库存落地）
//        if (prodCount >= SeckillConstant.CASH_STOCK_TO_DB
//                || (cacheStocks - prodCount) % SeckillConstant.CASH_STOCK_TO_DB == 0) {
//            SeckillCacheManager seckillCacheManager = (SeckillCacheManager) AopContext.currentProxy();
//            seckillCacheManager.flushStockToDb(seckillSkuId);
//        }
    }

    @Override
    public Integer getStockBySeckillSkuId(Long seckillId, Long skuId) {
        Integer totalStock = stockManager.getSeckillStock(
                new MultiDecrementBO(MultiStockEnum.SECKILL, null, null,
                        seckillId, skuId, null, null, null));
        return totalStock;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void flushStockToDb(Long seckillSkuId) {
        SeckillSku seckillSku = seckillSkuService.getSeckillSkuById(seckillSkuId);
        Integer cacheStock = this.getStockBySeckillSkuId(seckillSku.getSeckillId(), seckillSku.getSkuId());
        int changeStock = seckillSku.getSeckillStocks() - cacheStock;
        // 库存没变动，无需刷新
        if (changeStock == 0) {
            return;
        }
        // seckillSku库存、seckill库存刷新
        seckillSkuService.updateSeckillSkuStocks(seckillSkuId, -changeStock);
        seckillService.updateSeckillStocks(seckillSku.getSeckillId(), -changeStock);
    }

    @Override
    public void flushAndRemoveStockCache(Long seckillId) {
        List<SeckillSku> seckillSkus = seckillSkuService.list(new LambdaQueryWrapper<SeckillSku>()
                .eq(SeckillSku::getSeckillId, seckillId));
        if (CollectionUtils.isEmpty(seckillSkus)) {
            return;
        }
        // 刷新库存
        List<String> stockKeys = new ArrayList<>(16);
        SeckillCacheManager seckillCacheManager = (SeckillCacheManager) AopContext.currentProxy();
        for (SeckillSku seckillSku : seckillSkus) {
            seckillCacheManager.flushStockToDb(seckillSku.getSeckillSkuId());
            stockKeys.add(SeckillCacheNames.SECKILL_SKU_STOCKS_PREFIX + seckillSku.getSeckillSkuId());
        }
        // 移除缓存
        RedisUtil.del(stockKeys);
    }

    @Override
    public void unLockFailOrderStock() {
        while (true) {
            List<MultiDecrementBO> multiDecrementBOS = stockManager.unLockFailSeckillOrderStock(SeckillCacheNames.SECKILL_ORDER_LOCK_LOG);
            if (CollUtil.isEmpty(multiDecrementBOS)) {
                break;
            }
            int size = multiDecrementBOS.size();
            Set<String> orderNumberSet = multiDecrementBOS.stream().map(MultiDecrementBO::getOrderNumber).collect(Collectors.toSet());
            List<Order> dbOrderList = orderService.list(new LambdaQueryWrapper<Order>()
                    .eq(Order::getOrderType, OrderType.SECKILL.value())
                    .in(Order::getOrderNumber, orderNumberSet)
                    .select(Order::getOrderNumber)
            );
            Set<String> dbOrderNumberSet = dbOrderList.stream().map(Order::getOrderNumber).collect(Collectors.toSet());
            // 移除订单创建成功的库存锁定记录（订单创建成功的库存锁定记录，等取消订单时再处理）
            multiDecrementBOS.removeIf(skuStockLockDTO -> {
                if (dbOrderNumberSet.contains(skuStockLockDTO.getOrderNumber())) {
                    return true;
                }
                return false;
            });
            for (MultiDecrementBO multiDecrementBO : multiDecrementBOS) {
                stockManager.rollbackSecKillStock(multiDecrementBO.getActivityId(), multiDecrementBO.getSkuId(),
                        multiDecrementBO.getProdCount(), multiDecrementBO.getOrderNumber(), multiDecrementBO.getUserId(),
                        multiDecrementBO.getStockPointId());
            }
            stockManager.trimList(SeckillCacheNames.SECKILL_ORDER_LOCK_LOG, size);
        }
    }

    @Override
    public Integer selectNumByUser(Long seckillId, String userId) {
        String key = SeckillCacheNames.SECKILL_BUY_NUM + seckillId + Constant.UNDERLINE + userId;
        String num = stockManager.getByKey(key);
        if (StrUtil.isBlank(num)) {
            return 0;
        }
        return Integer.parseInt(num);
    }
}
